package wsz.mapper;

import wsz.sqlSession.SqlSession;

import java.io.Serializable;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * mapper代理
 * @author wsz
 * @date 2021/10/9 10:12
 **/
public class MapperProxy<T> implements InvocationHandler, Serializable {

    private SqlSession sqlSession;
    private Class mapperInterface;
    private Map<Method, MapperMethod> methodCache;

    public <T> MapperProxy(SqlSession sqlSession, Class mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // object对象中默认的方法
        if(Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }

        // interface新特性：接口提供的默认实现
        if (this.isDefaultMethod(method)) {
            return this.invokeDefaultMethod(proxy, method, args);
        }

        // 真正执行
        MapperMethod mapperMethod = this.cacheMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }

    /**
     * 获取缓存的method
     * @param method
     * @return
     */
    private MapperMethod cacheMapperMethod(Method method) {
        MapperMethod mapperMethod = this.methodCache.get(method);
        if (mapperMethod == null) {
            mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
            this.methodCache.put(method, mapperMethod);
        }
        return mapperMethod;
    }

    private Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable {
        Constructor<Lookup> constructor = Lookup.class.getDeclaredConstructor(Class.class, Integer.class);
        // 开启
        if (!constructor.isAccessible()) {
            constructor.setAccessible(true);
        }

        //
        Class<?> declaringClass = method.getDeclaringClass();
        return constructor
                .newInstance(declaringClass, 15)
                .unreflectSpecial(method, declaringClass)
                .bindTo(proxy)
                .invokeWithArguments(args);
    }

    /**
     * 判断是否默认的方法
     * @param method
     * @return
     */
    private boolean isDefaultMethod(Method method) {
        return (method.getModifiers() & 1033) == 1 && method.getDeclaringClass().isInterface();
    }
}
